package com.ruoyi.network.server;

import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.util.Date;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

import com.ruoyi.common.utils.Threads;
import com.ruoyi.network.common.Constant;
import com.ruoyi.network.common.NPSocket;
import com.ruoyi.network.config.AbstractHelper;

import lombok.extern.slf4j.Slf4j;

@Slf4j
public class ServerHelper extends AbstractHelper{
	// 监控线程
	public static ScheduledExecutorService monitor = Executors.newScheduledThreadPool(1);

	public static void start() {
		monitor.scheduleWithFixedDelay(() -> {
			clear();
		}, 15, 15, TimeUnit.SECONDS);
	}
	public synchronized static void distory(){
		//monitor.shutdown();
		Threads.shutdownAndAwaitTermination(monitor);
		for (NPSocket clientSocket : ServerContext.all.keySet()) {
			try {
				close(clientSocket);
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
	}
	//synchronized是为了保证 dispatch 与 clear必须是互斥操作
	private synchronized static void clear() {
		try {
			List<NPSocket> timeouts = new LinkedList<>();
			log.debug("开始扫描失效与超时连接");
			Date start = new Date();
			for (NPSocket clientSocket : ServerContext.all.keySet()) {
				Date lastTime = ServerContext.all.get(clientSocket);
				Date now = new Date();
				Date createTime = clientSocket.getCreateTime();
				boolean isOpen = clientSocket.getSocketChannel().isOpen();
				boolean isReadTimeout = comparseSec(lastTime, now) >= Constant.READ_TIMEOUT;
				boolean isBindTimeout = !clientSocket.isBind()
						&& comparseSec(createTime, now) >= Constant.BIND_TIMEOUT;
				if (!isOpen || isReadTimeout || isBindTimeout) {
					timeouts.add(clientSocket);
				}

			}
//			log.debug(
//					"超时连接找到" + timeouts.size() + "个,用时 : " + (new Date().getTime() - start.getTime()) + "毫秒");
//			log.debug("开始清除失效与超时连接");
			for (NPSocket socket : timeouts) {
				try {
					close(socket);
				} catch (Exception e) {
					e.printStackTrace();
				}
			}
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
//		show();

	}

	private static void close(NPSocket socket1) throws IOException {
		SocketChannel channel1 = socket1.getSocketChannel();
		NPSocket socket2 = ServerContext.pairs.get(channel1);
		if (socket2 != null) {
			try {
				SocketChannel channel2 = socket2.getSocketChannel();
				ServerContext.pairs.remove(channel1);
				ServerContext.all.remove(socket2);
				ServerContext.inners.remove(socket2);
				ServerContext.outers.remove(socket2);
				channel2.close();
				ServerContext.pairs.remove(channel2);
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
		if (socket1 != null) {
			try {
				ServerContext.all.remove(socket1);
				ServerContext.inners.remove(socket1);
				ServerContext.outers.remove(socket1);
				// 必须要将close放在最后处理,不然Map中找不到对应的value
				channel1.close();
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
	}
	
	//synchronized是为了保证 dispatch 与 clear必须是互斥操作
	private synchronized static void dispatch(Selector selector, NPSocket outerSocket) throws Exception {
		if (!outerSocket.isBind() && outerSocket.getSocketChannel().isOpen()) {
			Iterator<NPSocket> it = ServerContext.inners.keySet().iterator();
			while (it.hasNext()) {
				NPSocket innerSocket = it.next();
				if (!innerSocket.isBind() && outerSocket.getSocketChannel().isOpen() && !outerSocket.isBind()) {
					ServerContext.pairs.put(outerSocket.getSocketChannel(), innerSocket);
					ServerContext.pairs.put(innerSocket.getSocketChannel(), outerSocket);
					outerSocket.setBind(true);
					innerSocket.setBind(true);
					try {
						// 绑定完毕后才能注册为读,以保证报文顺序正确
						long start = System.currentTimeMillis();
						// register()方法是阻塞的,必须要selector唤醒才能返回
						outerSocket.getSocketChannel().register(outerSocket.getSelector(), SelectionKey.OP_READ);
						innerSocket.getSocketChannel().register(innerSocket.getSelector(), SelectionKey.OP_READ);
						long now = System.currentTimeMillis();
						log.debug("bind success ... " + (now - start));
						break;
					} catch (Exception e) {
						e.printStackTrace();
						try {
							close(outerSocket);
						} catch (IOException e1) {
							e1.printStackTrace();
						}
					}
				}
			}
		}
	}

	public static void close(SelectionKey key) {
		try {
			if (key != null) {
				key.cancel();
				SocketChannel channel1 = (SocketChannel) key.channel();
				if (channel1 != null) {
					close(channel1);
				}
			}

		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	private static void close(SocketChannel channel1) throws IOException {
		NPSocket socket2 = ServerContext.pairs.get(channel1);
		if (socket2 != null) {
			try {
				SocketChannel channel2 = socket2.getSocketChannel();
				NPSocket socket1 = ServerContext.pairs.get(channel2);

				ServerContext.pairs.remove(channel2);
				ServerContext.all.remove(socket2);
				ServerContext.inners.remove(socket2);
				ServerContext.outers.remove(socket2);
				if (socket1 != null) {
					ServerContext.pairs.remove(channel1);
					ServerContext.all.remove(socket1);
					ServerContext.inners.remove(socket1);
					ServerContext.outers.remove(socket1);
				}
				// 必须要将close放在最后处理,不然Map中找不到对应的value
				channel2.close();
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
		try {
			channel1.close();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	public static void handleRead(String name, Selector selector, SelectionKey key) throws Exception {
		SocketChannel channel1 = (SocketChannel) key.channel();
		NPSocket socket1 = ServerContext.pairs.get(channel1);
		if (socket1 != null) {// 绑定了转发Socket才能读数据
			// 创建ByteBuffer，并开辟一个1M的缓冲区,使用单例模式优化
			ByteBuffer buffer1 = ByteBuffer.allocate(1 * 1024);
			// 读取请求码流，返回读取到的字节数
			int readBytes = channel1.read(buffer1);
			// 读取到字节，对字节进行编解码
			if (readBytes > 0) {
				NPSocket socket2 = ServerContext.pairs.get(socket1.getSocketChannel());
				// 方式一:直接发送
				buffer1.flip();

				// 使用nio传输文件需要注意的是会出现粘包和服务器端缓冲区满的情况
				// 第一种情况，客户端发送30次数据，而服务器端只接收到18次的情况，这种情况出现主要是服务器端是以流的方式接收数据，它并不知道每次客户端传输数据的大小而造成的
				// 第二种情况是服务器端缓冲区满，导致客户端数据传输失败，这种情况下，需要判断传输int send =
				// client.write(sendBuffer)的send值，如果send值为0,则服务器端的数据缓冲区可能满了
				// 在上传大文件时异常,原因:在BIO中,当缓冲区满了就阻塞了,而对于非阻塞的NIO,缓冲区满了就立即返回0
				int isOk = socket1.getSocketChannel().write(buffer1);
				// 服务器端可能因为缓存区满，而导致数据传输失败，需要重新发送
				while (isOk == 0) {
					Thread.sleep(100L);
					isOk = socket1.getSocketChannel().write(buffer1);
				}
				buffer1.clear();
				Thread.sleep(10L);
				
				// 方式二:复制数据后再发送
				// 将缓冲区当前的limit设置为position=0，用于后续对缓冲区的读取操作
//				 buffer1.flip(); 
//				 // 根据缓冲区可读字节数创建字节数组 
//				 byte[] bytes = new byte[buffer1.remaining()]; //
//				 //从buffer中读取数据至字节数组中 
//				 buffer1.get(bytes);
//				 
//				 // 根据数组容量创建
//				 ByteBuffer ByteBuffer buffer2 = ByteBuffer.allocate(bytes.length);
//				 // 将字节数组复制到缓冲区 
//				 buffer2.put(bytes); 
//				 // flip操作 
//				 buffer2.flip();
//				 socket1.getSocketChannel().write(buffer2);

				// 更新通信时间
				ServerContext.all.put(socket1, new Date());
				ServerContext.all.put(socket2, new Date());

			} else if (readBytes < 0) {
				ServerHelper.close(key);
			}
		}
		
		//在带宽不好/接收端处理不过来的情况下,数据传送过快将导致写阻塞
		//解决方案一:控制转发速度
		//Thread.sleep(10L);
		//解决方案二:调整发送及接收缓冲区大小(TCP协议滑动窗口)
		//SocketChannel.setOption(StandardSocketOptions.SO_SNDBUF, 16 * 1024 * 1024);
		//SocketChannel.setOption(StandardSocketOptions.SO_RCVBUF, 16 * 1024 * 1024);
		//解决方案三:使用BIO实现;BIO同步阻塞式,相当于从底层机制控制了流量
		//由于BIO模型是阻塞的，服务器端的发送操作通常会受到操作系统套接字缓冲区大小的限制，以及网络带宽和延迟的限制。
		//一旦发送缓冲区满了，发送操作就会阻塞，直到缓冲区中有足够的空间可以继续发送数据

	}

	public static void handleAccept(String name, Selector selector, SelectionKey key, SocketChannel socketChannel)
			throws Exception {
		// 设置为非阻塞的
		socketChannel.configureBlocking(false);
		// 注册为读,绑定完毕后才能注册为读,以保证报文顺序正确
		// socketChannel.register(selector, SelectionKey.OP_READ);
		NPSocket socket = new NPSocket(name, selector, socketChannel, false);
		ServerContext.all.put(socket, new Date());
		if (name.equals("外网服务")) {
			log.debug("【Server】外网连接" + socketChannel.getRemoteAddress() + " 连接成功...");
			ServerContext.outers.put(socket, new Date());
		} else {
			log.debug("【Server】内网连接" + socketChannel.getRemoteAddress() + " 连接成功...");
			ServerContext.inners.put(socket, new Date());
		}
		// 不管连接情况,统一对所有未绑定的对象同步非阻塞分配,
		Iterator<NPSocket> it = ServerContext.outers.keySet().iterator();
		while (it.hasNext()) {
			NPSocket tmp = it.next();
			dispatch(selector, tmp);
		}
		// show();
	}

	public static void show() {
		log.debug("pairs " + ServerContext.pairs.size());
		log.debug("all " + ServerContext.all.size());
		log.debug("inners " + ServerContext.inners.size());
		log.debug("outers " + ServerContext.outers.size());
	}

}
